﻿using NServiceKit.Redis;
using NServiceKit.Redis.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace HobSoft.SoccerBet2014.Domain.Implementation
{
    using Core;
    using Models.Core;
    using SoccerBet2014.Common.Errors;
    using SoccerBet2014.Common.Queryable;

    public abstract class ReadWriteRedisRepository<T> : ReadonlyRedisRepository<T>, IReadWriteRepository<T>, IUniKeyReadWriteRepository<T>
                where T : class, IModel
    {
        public ReadWriteRedisRepository(PooledRedisClientManager _redisPoolManager)
            : base(_redisPoolManager)
        {

        }

        #region protected method
        protected virtual IRedisClient CreateRedisClient()
        {
            return redisClientManager.GetClient();
        }

        protected virtual IRedisTypedClient<TR> CreateRedisClient<TR>()
        {
            return redisClientManager.GetClient().As<TR>();
        }

        protected virtual int StoreAllModels<TEnt>(IList<TEnt> models) where TEnt : class, IModel
        {
            if (!models.Any()) return 0;
            using (var redisClient = CreateRedisClient())
            {
                using (var redisModel = redisClient.As<TEnt>())
                {
                    try
                    {
                        redisModel.StoreAll(models);
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }
            }
            return models.Count;
        }

        protected virtual int StoreAllModels<TEnt>(Expression<Func<TEnt, TEnt>> updateExpression, params object[] keys) where TEnt : class, IModel
        {
            if (!keys.Any()) return 0;
            int count = 0;
            using (var redisClient = CreateRedisClient())
            {
                using (var redisModel = redisClient.As<TEnt>())
                {
                    var models = redisModel.GetByIds(keys);
                    count = models.Count();
                    if (count == 0) return 0;
                    models.AsQueryable<TEnt>().Update(updateExpression);
                    redisModel.StoreAll(models);
                }
            }
            return count;
        }

        protected virtual int DeleteByIDS<TEnt>(IEnumerable<object> keys)
        {
            if (!keys.Any()) return 0;
            using (var redisClient = CreateRedisClient())
            {
                using (var redisModel = redisClient.As<TEnt>())
                {
                    redisModel.DeleteByIds(keys);
                }
            }
            return keys.Count();
        }
        #endregion

        #region IReadWriteRepository
        public virtual T AddOrUpdate(T model)
        {
            if (model == null) return null;

            using (var redisClient = CreateRedisClient())
            {
                using (var redisModel = redisClient.As<T>())
                {
                    redisModel.Store(model);
                }
            }
            return model;
        }

        public virtual int AddOrUpdate(IList<T> models)
        {
            models.IsNull().ThrowNull("models", "need models.");
            if (!models.Any()) return 0;
            StoreAllModels<T>(models);
            return models.Count;
        }

        public virtual bool Delete(T model)
        {
            if (model == null) return false;
            using (var redisClient = CreateRedisClient())
            {
                using (var redisModel = redisClient.As<T>())
                {
                    redisModel.Delete(model);
                }
            }
            return true;
        }

        public virtual bool Delete(params object[] id)
        {
            if (id.Length == 0) return false;
            using (var redisClient = CreateRedisClient())
            {
                using (var redisModel = redisClient.As<T>())
                {
                    redisModel.DeleteById(id);
                }
            }
            return true;
        }

        public virtual int DeleteMany(Expression<Func<T, bool>> predicate)
        {
            predicate.IsNull().ThrowNull("predicate", "need predicate.");

            int count = 0;
            using (var redisClient = CreateRedisClient())
            {
                using (var redisModel = redisClient.As<T>())
                {
                    var models = redisModel.GetAll().AsQueryable().Where(predicate);
                    count = models.Count();
                    if (count == 0) return 0;
                    using (var tran = redisModel.CreateTransaction())
                    {
                        foreach (var mod in models)
                        {
                            tran.QueueCommand(m => m.Delete(mod));
                        }
                        tran.Commit();
                    }
                }
            }
            return count;
        }

        public virtual int Update(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> updateExpression)
        {
            predicate.IsNull().ThrowNull("predicate", "need predicate.");
            updateExpression.IsNull().ThrowNull("updateExpression", "need updateExpression.");

            int count = 0;
            using (var redisClient = CreateRedisClient())
            {
                using (var redisModel = redisClient.As<T>())
                {
                    var models = redisModel.GetAll().AsQueryable().Where(predicate);
                    count = models.Count();
                    if (count == 0) return 0;
                    models.Update(updateExpression);
                    redisModel.StoreAll(models);
                }
            }
            return count;
        }
        #endregion

        #region IUniKeyReadWriteRepository
        public virtual int DeleteMany(params object[] keys)
        {
            keys.IsNull().ThrowNull("keys", "need keys");
            if (!keys.Any())
                return 0;
            return DeleteByIDS<T>(keys);
        }

        public virtual int Update(Expression<Func<T, T>> updateExpression, params object[] keys)
        {
            keys.IsNull().ThrowNull("keys", "need keys");
            updateExpression.IsNull().ThrowNull("updateExpression", "need updateExpression");
            if (!keys.Any())
                return 0;

            return StoreAllModels<T>(updateExpression, keys);
        }
        #endregion
    }
}
